//
// Created by richa on 2019/1/21.
//

#include <iostream>
#include "ModbusTcp.h"

#include <cstdio>
#include <unistd.h>
#include <cstring>
#include <cstdlib>

using namespace std;
//#define OS_WIN32
#ifdef OS_WIN32
//static int _modbus_tcp_init_win32()
//{
//    /* Initialise Windows Socket API */
//    WSADATA wsaData;
//
//    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
//        fprintf(stderr, "WSAStartup() returned error code %d\n",
//                (unsigned int)GetLastError());
//        errno = EIO;
//        return -1;
//    }
//    return 0;
//}
#endif

void ModbusTcp::setListenPort(int port) {
    this->listenPort = port;
}

void ModbusTcp::startListen() {
    this->listenThread = new thread(listenThreadFunc, this);
}

void ModbusTcp::listenThreadFunc(ModbusTcp *p) {
    p->ctx = modbus_new_tcp(p->ip_addr, p->listenPort);
    cout<<"ModbusTcp:: while to start listen loop..."<<endl;
    for(;;){
        cout<<"ModbusTcp:: start listen ..."<<endl;
        p->server_socket = modbus_tcp_listen(p->ctx, p->max_connection);
        if(p->server_socket == -1){
            cout<<"ModbusTcp::无法Listen Tcp连接..."<<endl;
            modbus_free(p->ctx);
            return;
        }
        socklen_t addrLen;
        struct sockaddr_in clientAddr{};
        int clientFd;

        addrLen = sizeof(clientAddr);
        memset(&clientAddr, 0, sizeof(clientAddr));
        clientFd = (accept((p->server_socket), (struct sockaddr *)&clientAddr, &addrLen));
        if(clientFd == -1){
            cout << "accept 错误" << endl;
        } else {
            p->socketFdSet.insert(clientFd);
            if(clientFd > p->fdMax){
                p->fdMax = clientFd;
            }
            printf("New connection from %s:%d on socket %d\n",
                   inet_ntoa(clientAddr.sin_addr), clientAddr.sin_port, clientFd);
            if(p->replyThread == nullptr){
                p->replyThread = new thread(replyThreadFunc, p);
            }
        }
        close(p->server_socket);
    }
}

ModbusTcp::~ModbusTcp() {
    modbus_free(this->ctx);
    this->listenThread->detach();
    this->replyThread->detach();
    if(this->server_socket != -1){
        close(this->server_socket);
    }
}

ModbusTcp::ModbusTcp(const char *ip_addr, modbus_mapping_t *modbus_mapping)
        : ip_addr(ip_addr), modbus_mapping(modbus_mapping) {
    cout << "modbus tcp constructor, modbus mapping is " << modbus_mapping << endl;
    replyThread = nullptr;
}

void ModbusTcp::replyThreadFunc(ModbusTcp *p) {
    int rc;
    uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
    fd_set fdSet;
#ifdef OS_WIN32
    std::cout << "abc" << endl;
    if (_modbus_tcp_init_win32() == -1) {
        return;
    }
#endif
    for(;;){
        FD_ZERO(&fdSet);
//        cout<<"reply thread is looping "<<endl;
        std::set<int>::iterator it;
        for(it=p->socketFdSet.begin(); it!=p->socketFdSet.end(); it++){
            FD_SET(*it, &fdSet);
        }
        struct timeval tv{};
        tv.tv_sec = 1;
        tv.tv_usec = 0;

        if(select(p->fdMax+1, &fdSet, nullptr, nullptr, &tv) == -1){
            cout<<"ModbusTcp:: select return error! "<<endl;
            return;
        }
        int client_socket;
        for(client_socket=0; client_socket <= p->fdMax; client_socket++){
            if(!FD_ISSET(client_socket, &fdSet)){
                continue;
            }
            modbus_set_socket(p->ctx, client_socket);
            rc = modbus_receive(p->ctx, query);
//            cout << "rc = " << rc << " modbus_mapping is NULL ? " << p->modbus_mapping << endl;
//            printf("replay modbus_mapping is %p\n", p->modbus_mapping);
            if(rc > 0 && p->modbus_mapping){
                modbus_reply(p->ctx, query, rc, p->modbus_mapping);
            }else if(rc == -1){
                close(client_socket);
                p->socketFdSet.erase(client_socket);
                if(client_socket == p->fdMax){
                    p->fdMax--;
                }
            }
        }
    }
}
